#include <interface.h>

#if HAVE_CONFIG_H
# include <config.h>
#endif

#include <book.h>
#include <errors.h>
#include <eval.h>
#include <fen.h>
#include <game.h>
#include <movegen.h>
#include <perft.h>
#include <pgn.h>
#include <san.h>
#include <search.h>
#include <stats.h>
#include <suite.h>
#include <utils.h>

#include <assert.h>
#include <inttypes.h>
#include <search.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <time.h>
#if HAVE_UNISTD_H
#  include <unistd.h>
#endif

#define POLLING_INTERVAL 250  // milliseconds
#define MAX_ARGS 10


/*
 * TODO:
 * - complete the xboard protocol.
 */


const char* ignored[] = {
  "accepted",
  "computer",
  "easy",
  "hard",
  "rejected",
  0
};


static int parse_time(const char* s, int defmult)
{
  char* end;
  long mins = strtol(s, &end, 10);
  if (mins < 0)
    return -1;
  if (*end == '\0')
    return mins * defmult;
  else if (*end != ':')
    return -1;
  
  long secs = strtol(end + 1, &end, 10);
  if (*end == '\0' || secs < 0)
    return -1;
  
  return secs + mins * 60;
}


static void shutdown(interface_t* iface)
{
  iface->state = is_quit;
  search_cancel();
}


static int game_end(interface_t* iface)
{
  const game_t* g = &(iface->game);
  if (game_is_checkmate(g)) {
    const position_t* pos = game_cur_position(g);
    if (position_get_stm(pos) == WHITE)
      printf("0-1 {Black mates}\n");
    else
      printf("1-0 {White mates}\n");
    return 1;
  }
  if (game_is_stalemate(g)) {
    printf("1/2-1/2 {Stalemate}\n");
    return 1;
  }
  if (game_draw_by_material(g)) {
    printf("1/2-1/2 {Draw by insufficient material}\n");
    return 1;
  }
  if (game_draw_by_repetition(g)) {
    printf("1/2-1/2 {Draw by threefold repetition}\n");
    return 1;
  }
  if (game_draw_by_50(g)) {
    printf("1/2-1/2 {Draw by fifty moves rule}\n");
    return 1;
  }
  return 0;
}


static void cmd_book(interface_t* iface, int argc, char* argv[])
{
  if (!book_load(argv[1]))
    printf("Error (book): %s\n", error_string(error_last()));
}


static void cmd_divide(interface_t* iface, int argc, char* argv[])
{
  char* end;
  long depth = strtol(argv[1], &end, 10);
  if (*end != '\0' || depth < 1) {
    printf("Error (invalid argument): %s\n", argv[1]);
    return;
  }

  divide_position(&(iface->game), depth);
}


static void cmd_dumppgn(interface_t* iface, int argc, char* argv[])
{
  pgn_print(stdout, &(iface->game));
}


static void cmd_force(interface_t* iface, int argc, char* argv[])
{
  search_cancel();
  iface->state = is_force;
}


static void cmd_getboard(interface_t* iface, int argc, char* argv[])
{
  const position_t* pos = game_cur_position(&(iface->game));
  if (!print_fen(stdout, pos))
    printf("Error (internal): memory allocation");
  printf("\n");
}


static void cmd_go(interface_t* iface, int argc, char* argv[])
{
  iface->state = is_play;
  search_start(iface);
  game_end(iface);
}


static void cmd_level(interface_t* iface, int argc, char* argv[])
{
  char* end;
  long tc = strtol(argv[1], &end, 10);
  if (*end != '\0' || tc < 0) {
    printf("Error (invalid argument): %s\n", argv[1]);
    return;
  }

  int time = parse_time(argv[2], 60);
  if (time < 0) {
    printf("Error (invalid argument): %s\n", argv[2]);
    return;
  }

  if (time == 0)
    time = 10;  // special ICS rule

  long increment = strtol(argv[3], &end, 10);
  if (*end != '\0' || increment < 0) {
    printf("Error (invalid argument): %s\n", argv[3]);
    return;
  }

  iface->tc_moves = tc;
  iface->tc_time = time * 1000;
  iface->tc_otime = time * 1000;
  iface->increment = increment * 1000;
}


static void cmd_makebook(interface_t* iface, int argc, char* argv[])
{
  // makebook pgn book plies mingames
  char* end;
  long plies = strtol(argv[3], &end, 10);
  if (*end != '\0' || plies < 0) {
    printf("Error (invalid argument): %s\n", argv[3]);
    return;
  }
  long mingames = strtol(argv[4], &end, 10);
  if (*end != '\0' || mingames < 1) {
    printf("Error (invalid argument): %s\n", argv[4]);
    return;
  }
  if (!book_make(argv[1], argv[2], plies, mingames)) {
    printf("%s\n", error_string(error_last()));
    printf("Error (book): error creating the opening book\n");
  }
}


static void cmd_memory(interface_t* iface, int argc, char* argv[])
{
  char* end;
  long mbytes = strtol(argv[1], &end, 10);
  if (*end != '\0' || mbytes < 0) {
    printf("Error (invalid argument): %s\n", argv[1]);
    return;
  }
  if (!ttable_resize(mbytes)) {
    printf("Error (internal): memory allocation\n");
    return;
  }
}


static void cmd_new(interface_t* iface, int argc, char* argv[])
{
  iface->depth_limit = 0;  // no limit
  iface->resignation_count = 0;
  game_set_init_position(&(iface->game));
  iface->state = is_playother;
  eval_reset_max_positional_value();
}


static void cmd_nopost(interface_t* iface, int argc, char* argv[])
{
  iface->show_think = 0;
}


static void cmd_otim(interface_t* iface, int argc, char* argv[])
{
  char* end;
  long csecs = strtol(argv[1], &end, 10);
  if (*end != '\0') {
    printf("Error (invalid argument): %s\n", argv[1]);
    return;
  }
  if (csecs < 0)
    csecs = 0;

  iface->tc_otime = csecs * 10;
}


static void cmd_perft(interface_t* iface, int argc, char* argv[])
{
  char* endp;
  long depth = strtol(argv[1], &endp, 10);
  if (*endp != '\0' || depth < 1) {
    printf("Error (invalid argument): %s\n", argv[1]);
    return;
  }

  struct timeval start;
  gettimeofday(&start, 0);
  uint64_t res = perft_position(&(iface->game), depth);
  struct timeval end;
  gettimeofday(&end, 0);

  double diff = end.tv_sec - start.tv_sec;
  diff += (end.tv_usec - start.tv_usec) * 1e-6;

  printf("%" PRIu64 " (%g seconds)\n", res, diff);
}


static void cmd_ping(interface_t* iface, int argc, char* argv[])
{
  char* endp;
  long N = strtol(argv[1], &endp, 10);
  if (*endp != '\0') {
    printf("Error (invalid argument): %s\n", argv[1]);
    return;
  }
  printf("pong %ld\n", N);
}


static void cmd_post(interface_t* iface, int argc, char* argv[])
{
  iface->show_think = 1;
}


static void cmd_protover(interface_t* iface, int argc, char* argv[])
{
  char* endp;
  long version = strtol(argv[1], &endp, 10);
  if (*endp != '\0') {
    printf("Error (invalid argument): %s\n", argv[1]);
    return;
  }
  if (version < 2) {
    printf("Error (unsupported protocol version): %ld\n", version);
    return;
  }

  printf("feature done=0\n");
  printf("feature ping=1\n");
  printf("feature setboard=1\n");
  printf("feature playother=0\n"); // =1
  printf("feature san=1\n");
  printf("feature usermove=1\n");
  printf("feature time=1\n");
  printf("feature draw=0\n");  // =1
  printf("feature sigint=0\n");
  printf("feature sigterm=0\n");
  printf("feature reuse=1\n");
  printf("feature analyze=0\n");  // =1
  printf("feature myname=\"%s-%s\"\n", PACKAGE_STRING, SVNREV);
  printf("feature variants=\"normal\"\n");
  printf("feature colors=0\n");
  printf("feature ics=0\n");
  printf("feature name=0\n");
  printf("feature pause=0\n"); // =1
  printf("feature nps=0\n");  // =1
  printf("feature debug=0\n");  // =1
  printf("feature memory=1\n");
  printf("feature smp=0\n");  // =1
  printf("feature done=1\n");
}


static void cmd_question_mark(interface_t* iface, int argc, char* argv[])
{
  search_hurry();
}


static void cmd_quit(interface_t* iface, int argc, char* argv[])
{
  shutdown(iface);
}


static void cmd_random(interface_t* iface, int argc, char* argv[])
{
  eval_set_random(1);
}


static void cmd_readpgn(interface_t* iface, int argc, char* argv[])
{
  FILE* f = fopen(argv[1], "rt");
  if (f == 0) {
    printf("Error (I/O): error opening '%s'\n", argv[1]);
    return;
  }
  int ret = pgn_read(f, &(iface->game));
  if (ret < 0) {
    printf("Error (I/O): error reading from '%s'\n", argv[1]);
  } else if (ret == 0) {
    printf("Error (I/O): no games in file '%s'\n", argv[1]);
  } else {
    iface->state = is_force;
  }
  fclose(f);
}


static void cmd_result(interface_t* iface, int argc, char* argv[])
{
  search_cancel();
  iface->state = is_force;
  eval_reset_max_positional_value();
}


static void cmd_sd(interface_t* iface, int argc, char* argv[])
{
  char* endp;
  long depth = strtol(argv[1], &endp, 10);
  if (*endp != '\0' || depth < 1) {
    printf("Error (invalid argument): %s\n", argv[1]);
    return;
  }
  iface->depth_limit = depth;
}


static void cmd_setboard(interface_t* iface, int argc, char* argv[])
{
  size_t buflen = 0;
  for (int i = 1; i < argc; i++)
    buflen += strlen(argv[i]) + 1;
  char* buf = (char*)malloc(buflen);
  if (!buf) {
    printf("Error (internal): memory allocation\n");
    shutdown(iface);
  }

  sprintf(buf, "%s %s %s %s %s %s",
	  argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]);
  position_t pos;
  if (!decode_fen(buf, &pos)) {
    printf("Error (Invalid FEN string): %s\n", buf);
  } else {
    iface->resignation_count = 0;
    if (!game_set_start_position(&(iface->game), &pos)) {
      printf("tellusererror Illegal position\n");
      printf("Error (fen): %s\n", error_string(error_last()));
    }
  }
  free(buf);
}


static void cmd_st(interface_t* iface, int argc, char* argv[])
{
  int secs = parse_time(argv[1], 1);
  if (secs < 1) {
    printf("Error (invalid argument): %s\n", argv[1]);
    return;
  }
  iface->time_limit = secs * 1000;
}


static void cmd_suite(interface_t* iface, int argc, char* argv[])
{
  if (!strcmp(argv[1], "perft"))
    perftsuite(argv[2]);
  else if (!strcmp(argv[1], "search"))
    searchsuite(argv[2], iface);
  else if (!strcmp(argv[1], "eval"))
    evalsuite(argv[2]);
  else
    printf("Error (invalid argument): %s\n", argv[1]);
}


static void cmd_time(interface_t* iface, int argc, char* argv[])
{
  char* end;
  long csecs = strtol(argv[1], &end, 10);
  if (*end != '\0') {
    printf("Error (invalid argument): %s\n", argv[1]);
    return;
  }
  if (csecs < 0)
    csecs = 0;

  iface->tc_time = csecs * 10;
}


static void cmd_usermove(interface_t* iface, int argc, char* argv[])
{
  move_t m;
  game_t* game = &(iface->game);
  const position_t* pos = game_cur_position(game);
  if (!move_from_san_string(argv[1], pos, &m)) {
    printf("Error (invalid move): %s\n", argv[1]);
  } else {
    m = movegen_test_legality(game_cur_position(game), m);
    if (m == NULL_MOVE) {
      printf("Illegal move: %s\n", argv[1]);
    } else if (!game_make_move(game, m)) {
      printf("Error (internal): move execution\n");
      iface->state = is_force;
    } else if (iface->state == is_play || 
	       iface->state == is_playother) {
      iface->state = is_play;
      if (!game_end(iface)) {
	search_start(iface);
	game_end(iface);
      }
    }
  }
}


static void cmd_xboard(interface_t* iface, int argc, char* argv[])
{
  iface->state = is_force;
  iface->prompt[0] = '\0';
  setbuf(stdout, 0);
  setbuf(stdin, 0);
}


static int ignore_command(const char* cmd)
{
  for (const char** p = ignored; *p != 0; ++p)
    if (!strcmp(*p, cmd))
      return 1;
  return 0;
}


static int handle_command(interface_t* iface,
			  const char* cmd,
			  void (*callback)(interface_t*, int, char*[]),
			  int naccepted,
			  int argc,
			  char* argv[])
{
  if (strcmp(cmd, argv[0]))
    return 0;

  if (naccepted >= 0) {
    if (argc - 1 == naccepted + 1) {
      printf("Error (extra argument): %s\n", argv[argc - 1]);
      return 1;
    } else if (argc - 1 > naccepted) {
      printf("Error (extra arguments):");
      for (int i = naccepted + 1; i < argc; i++)
	printf(" %s", argv[i]);
      printf("\n");
      return 1;
    } else if (argc - 1 < naccepted) {
      printf("Error (missing arguments): ");
      printf("%s expects %d arguments, got %d\n",
	     argv[0], naccepted, argc - 1);
      return 1;
    }
  }

  callback(iface, argc, argv);
  return 1;
}


static void exec_command(interface_t* iface, int argc, char* argv[])
{
  assert(argc > 0);

  if (argv[0][0] == '#')
    return;

  int ret = (handle_command(iface, "book", cmd_book, 1, argc, argv) ||
	     handle_command(iface, "divide", cmd_divide, 1, argc, argv) ||
	     handle_command(iface, "dumppgn", cmd_dumppgn, 0, argc, argv) ||
	     handle_command(iface, "force", cmd_force, 0, argc, argv) ||
	     handle_command(iface, "getboard", cmd_getboard, 0, argc, argv) ||
	     handle_command(iface, "go", cmd_go, 0, argc, argv) ||
	     handle_command(iface, "level", cmd_level, 3, argc, argv) ||
	     handle_command(iface, "memory", cmd_memory, 1, argc, argv) ||
	     handle_command(iface, "makebook", cmd_makebook, 4, argc, argv) ||
	     handle_command(iface, "new", cmd_new, 0, argc, argv) ||
	     handle_command(iface, "nopost", cmd_nopost, 0, argc, argv) ||
	     handle_command(iface, "otim", cmd_otim, 1, argc, argv) ||
	     handle_command(iface, "perft", cmd_perft, 1, argc, argv) ||
	     handle_command(iface, "ping", cmd_ping, 1, argc, argv) ||
	     handle_command(iface, "post", cmd_post, 0, argc, argv) ||
	     handle_command(iface, "protover", cmd_protover, 1, argc, argv) ||
	     handle_command(iface, "?", cmd_question_mark, 0, argc, argv) ||
	     handle_command(iface, "quit", cmd_quit, 0, argc, argv) ||
	     handle_command(iface, "random", cmd_random, 0, argc, argv) ||
	     handle_command(iface, "readpgn", cmd_readpgn, 1, argc, argv) ||
	     handle_command(iface, "result", cmd_result, -1, argc, argv) ||
	     handle_command(iface, "sd", cmd_sd, 1, argc, argv) ||
	     handle_command(iface, "setboard", cmd_setboard, 6, argc, argv) ||
	     handle_command(iface, "st", cmd_st, 1, argc, argv) ||
	     handle_command(iface, "suite", cmd_suite, 2, argc, argv) ||
	     handle_command(iface, "time", cmd_time, 1, argc, argv) ||
	     handle_command(iface, "usermove", cmd_usermove, 1, argc, argv) ||
	     handle_command(iface, "xboard", cmd_xboard, 0, argc, argv) ||
	     ignore_command(argv[0]));

  if (!ret && argc == 1) {
    // Try to interpret the command as a move.
    char* argv2[2] = {"usermove", argv[0]};
    ret = handle_command(iface, "usermove", cmd_usermove, 1, 2, argv2);
  }

  if (!ret)
    printf("Error (uknown command): %s\n", argv[0]);
}


void interface_main_loop(interface_t* iface)
{
  assert(iface != 0);

  printf("\nLaggard v%s", PACKAGE_STRING);

#if HAVE_GETHOSTNAME
  char hostbuf[65];
  gethostname(hostbuf, 65);
  hostbuf[64] = '\0';
  printf(" on %s", hostbuf);
#endif

  printf("\n\n");

  iface->state = is_force;
  iface->searching = 0;

  printf("%s", iface->prompt);
  fflush(stdout);
  while (iface->state != is_quit) {
    while (interface_handle_input(iface)) {
      if (iface->state == is_quit)
	break;
      printf("%s", iface->prompt);
      fflush(stdout);
    }
    struct timespec req;
    struct timespec rem;
    req.tv_sec = POLLING_INTERVAL / 1000;
    req.tv_nsec = 1000000 * (POLLING_INTERVAL % 1000);
    nanosleep(&req, &rem);
  }
}


/* Similar to GNU getline. */
int readline(int fd, char** buf, size_t* buflen)
{
  char* line = *buf;
  size_t linelen = *buflen;
  if (linelen == 0 || line == 0) {
    line = (char*)malloc(64 * sizeof(char));
    if (!line)
      return -1;
    linelen = 64;
    *buflen = linelen;
    *buf = line;
  }
   
  char c;
  int n = 0;
  for (;;) {
    ssize_t ret = read(fd, &c, 1);
    if (ret < 0)
      return -1;
    if (ret == 0)
      break;
    if (n == linelen - 2) {
      linelen *= 2;
      line = (char*)realloc(line, linelen * sizeof(char));
      *buflen = linelen;
      *buf = line;
    }
    line[n++] = c;
    if (c == '\n')
      break;
  }
  line[n] = '\0';
  return n;
}


/*
 * Read a line and possibly execute the command.
 *
 * Return 1 if some command has been successfully executed.
 */
int interface_handle_input(interface_t* iface)
{
  assert(iface != 0);

  /* If reading commands from a script, their execution is disabled
     during search. */
  if (iface->searching && iface->input != 0)
    return 0;

  struct timespec timeout;
  timeout.tv_sec = 0;
  timeout.tv_nsec = 0;

  int fd = iface->input;
  fd_set rfds;
  FD_ZERO(&rfds);
  FD_SET(fd, &rfds);

  int ret = pselect(fd + 1, &rfds, 0, 0, &timeout, 0);
  if (ret < 0) {
    printf("Error (internal): I/O error\n");
    shutdown(iface);
    return 1;
  }
  if (!ret)
    return 0;
  
  char* line = 0;
  size_t linebuflen = 0;
  int len = readline(fd, &line, &linebuflen);

  if (len <= 0) {
    free(line);
    shutdown(iface);
    return 1;
  }

  if (len > 0 && line[len - 1] == '\n')
    line[len - 1] = '\0';

  char* argv[MAX_ARGS];
  int argc = 0;
  char* last = line;

  argv[0] = strtok_r(line, " \t", &last);
  argc = 1;
  if (argv[0] == 0) {
    free(line);
    return 0;
  }

  while(argc < MAX_ARGS - 1) {
    argv[argc] = strtok_r(0, " \t", &last);
    if (argv[argc] == 0)
      break;
    ++argc;
  }
  if (argc == MAX_ARGS - 1)
    argv[argc++] = last;

  exec_command(iface, argc, argv);
  free(line);

  return 1;
}


int interface_init(interface_t* iface)
{
  iface->state = is_force;
  iface->depth_limit = 0;  // no limit
  iface->time_limit = 0;  // no limit
  iface->tc_moves = 0; // the whole game
  iface->tc_time = 3600000;  // assume one hour
  iface->tc_otime = 3600000;  // assume one hour
  iface->increment = 0;  // no increment
  iface->show_think = 1;
  strcpy(iface->prompt, ">>> ");
  iface->input = 0;
  iface->searching = 0;
  iface->resignation_count = 0;

  if (!game_init(&(iface->game)))
    return 0;
  game_set_init_position(&(iface->game));
  return 1;
}


int interface_copy(const interface_t* src, interface_t* dst)
{
  dst->state = src->state;
  dst->depth_limit = src->depth_limit;
  dst->time_limit = src->time_limit;
  dst->tc_moves = src->tc_moves;
  dst->tc_time = src->tc_time;
  dst->tc_otime = src->tc_otime;
  dst->increment = src->increment;
  dst->show_think = src->show_think;
  strcpy(dst->prompt, src->prompt);
  dst->input = src->input;
  dst->searching = src->searching;
  dst->resignation_count = src->resignation_count;
  return game_copy(&(src->game), &(dst->game));
}


void interface_set_input(interface_t* iface, int fd)
{
  assert(iface != 0);
  assert(fd >=0);
  iface->input = fd;
  if (fd == 0)
    strcpy(iface->prompt, ">>> ");
  else
    strcpy(iface->prompt, "");
}


void interface_cleanup(interface_t* iface)
{
  assert(iface != 0);
  game_cleanup(&(iface->game));
}
